package com.ehsunbehravesh.utils.update;

import com.ehsunbehravesh.Utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;
import sun.misc.BASE64Encoder;

/**
 *
 * @author ehsun.behravesh
 */
public class Updater extends Observable implements Runnable {

  public static final String NO_UPDATE_FOUND = "no update found";
  public static final String UPDATE_FOUND = "update found";
  public static final String DOWNLOAD_FINISHED = "download finished";
  public static final String DOWNLOADED_HASH_CALCULATED = "downloaded hash calculated";
  public static final String HASH_DOWNLOADED = "hash downloaded";
  public static final String HASH_NOT_MATCH = "hash not match";
  public static final String OLD_FILE_DELETE_FAILED = "old file delete failed";
  public static final String UPDATE_FINISHED = "update finished";
  public static final String NEW_FILE_RENAME_FAILED = "new file rename failed";
  private URL versionUrl, downloadUrl, hashUrl;
  private double currentVersion, newVersion;
  private File existingFile, tempFile;  
  private String downloadedHash, hash;

  public Updater(URL versionUrl, URL downloadUrl, URL hashUrl, double currentVersion) {
    this.versionUrl = versionUrl;
    this.downloadUrl = downloadUrl;
    this.hashUrl = hashUrl;
    this.currentVersion = currentVersion;
  }

  public Updater(String versionUrl, String downloadUrl, String hashUrl, double currentVersion) throws MalformedURLException {
    this.versionUrl = new URL(versionUrl + "?ts" + System.currentTimeMillis());
    this.downloadUrl = new URL(downloadUrl);
    this.hashUrl = new URL(hashUrl + "?ts" + System.currentTimeMillis());
    this.currentVersion = currentVersion;
  }

  public double getNewVersion() {
    return newVersion;
  }  
  
  public void setExistingFile(File existingFile) {
    this.existingFile = existingFile;
  }

  @Override
  public void run() {
    try {
      URLConnection conn = versionUrl.openConnection();
      try (InputStream inputStream = conn.getInputStream()) {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String line = bufferedReader.readLine();
        newVersion = Double.parseDouble(line);

        if (newVersion > currentVersion) {
          setChanged();
          notifyObservers(UPDATE_FOUND);

          createTempFile();
          doanloadFile(downloadUrl, tempFile);

          setChanged();
          notifyObservers(DOWNLOAD_FINISHED);

          downloadedHash = calculateHash(new File(Utils.getPathOfJar(true) + "/Varzesh3.jar-" + newVersion));
          setChanged();
          notifyObservers(DOWNLOADED_HASH_CALCULATED);

          conn = hashUrl.openConnection();

          try (InputStream hashInputStream = conn.getInputStream()) {
            BufferedReader hashBufferedReader = new BufferedReader(new InputStreamReader(hashInputStream));
            hash = hashBufferedReader.readLine();
            if (hash != null) {
              hash = hash.trim();
              setChanged();
              notifyObservers(HASH_DOWNLOADED);
            }

            if (hash.equals(downloadedHash)) {
              File oldFile = new File(Utils.getPathOfJar(true) + "/Varzesh3.jar");
              if (oldFile.delete()) {
                File newFile = new File(Utils.getPathOfJar(true) + "/Varzesh3.jar-" + newVersion);
                if (newFile.renameTo(oldFile)) {
                  setChanged();
                  notifyObservers(UPDATE_FINISHED);
                } else {
                  setChanged();
                  notifyObservers(NEW_FILE_RENAME_FAILED);
                }
              } else {
                setChanged();
                notifyObservers(OLD_FILE_DELETE_FAILED);
              }
            } else {
              setChanged();
              notifyObservers(HASH_NOT_MATCH);
              System.out.println(hash);
              System.out.println(downloadedHash);
            }
          }

        } else {
          setChanged();
          notifyObservers(NO_UPDATE_FOUND);
        }
      } catch (FileNotFoundException ex) {
        Logger.getLogger(Updater.class.getName()).log(Level.SEVERE, null, ex);
      } catch (IOException | NoSuchAlgorithmException ex) {
        Logger.getLogger(Updater.class.getName()).log(Level.SEVERE, null, ex);
      }
    } catch (IOException | NumberFormatException ex) {
      setChanged();
      notifyObservers(ex);
    }
  }

  private byte[] convertToPrimitiveArray(List<Byte> list) {
    byte[] bytes = new byte[list.size()];
    int i = 0;
    for (byte b : list) {
      bytes[i++] = b;
    }

    return bytes;
  }

  private String toB64(byte[] bytes) {
    BASE64Encoder encoder = new BASE64Encoder();
    String encode = encoder.encode(bytes);

    return encode;
  }

  private void createTempFile() {
    tempFile = new File(Utils.getPathOfJar(true) + "/" + existingFile.getName() + "-" + newVersion);
    System.out.println(tempFile.getAbsoluteFile());
  }

  private void doanloadFile(URL url, File tempFile) throws FileNotFoundException, IOException {
    ReadableByteChannel rbc = Channels.newChannel(url.openStream());
    FileOutputStream fos = new FileOutputStream(tempFile);
    fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
  }

  private String calculateHash(File file) throws FileNotFoundException, IOException, NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    byte[] content = new byte[(int) file.length()];
    try (InputStream inputStream = new FileInputStream(file)) {
      inputStream.read(content, 0, content.length);
    }
    byte[] digest1 = digest.digest(content);
    BASE64Encoder encoder = new BASE64Encoder();
    return encoder.encode(digest1);
  }
}
